﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;
using System.Reflection.Emit;

namespace JV.MVVM {
    /// <summary>
    /// Delegate created by the Emitter class to the get portion of a property
    /// </summary>
    /// <param name="source">Object from which it will get the property value</param>
    /// <returns>The value stored in the ID of the given object</returns>
    internal delegate object GetHandler( object source );

    /// <summary>
    /// Delegate created by the Emitter class to the set portion of the property
    /// </summary>
    /// <param name="source">Object from which it will set the property value</param>
    /// <param name="value">Value to be set in the property of the object</param>
    internal delegate void SetHandler( object source, object value );

    /// <summary>
    /// Delegate created by the Emitter class to create a new object
    /// </summary>
    /// <returns>A newly created object</returns>
    internal delegate object InstantiateObjectHandler( );

    /// <summary>
    /// Class with static methods that will emit the proper delegates
    /// </summary>
    class Emmiter {

        /// <summary>
        /// Creates the delegate that will create a new object of a given type
        /// </summary>
        /// <param name="type">Type for which the objects will be created</param>
        /// <returns>The delegate that will be used to create new objects</returns>
        public static InstantiateObjectHandler CreateInstantiateObjectHandler( Type type ) {
            ConstructorInfo constructorInfo = type.GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[ 0 ], null );
            if ( constructorInfo == null ) {
                throw new ApplicationException( string.Format( "The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type ) );
            }

            DynamicMethod dynamicMethod = new DynamicMethod( "InstantiateObject", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof( object ), null, type, true );
            ILGenerator generator = dynamicMethod.GetILGenerator( );
            generator.Emit( OpCodes.Newobj, constructorInfo );
            generator.Emit( OpCodes.Ret );
            return ( InstantiateObjectHandler )dynamicMethod.CreateDelegate( typeof( InstantiateObjectHandler ) );
        }

        /// <summary>
        /// Creates the delegate that will get values out of properties
        /// </summary>
        /// <param name="propertyInfo">Reflected property information</param>
        /// <returns>The delegate that will get values out of object's property</returns>
        public static GetHandler CreateGetHandler( PropertyInfo propertyInfo ) {
            MethodInfo getMethodInfo = propertyInfo.GetGetMethod( true );
            DynamicMethod dynamicGet = CreateGetDynamicMethod( propertyInfo.DeclaringType );
            ILGenerator getGenerator = dynamicGet.GetILGenerator( );

            getGenerator.Emit( OpCodes.Ldarg_0 );
            getGenerator.Emit( OpCodes.Call, getMethodInfo );
            BoxIfNeeded( getMethodInfo.ReturnType, getGenerator );
            getGenerator.Emit( OpCodes.Ret );

            return ( GetHandler )dynamicGet.CreateDelegate( typeof( GetHandler ) );
        }

        /// <summary>
        /// Creates a get handler to obtain the values of a iOS in an object
        /// </summary>
        /// <param name="fieldInfo">Reflected iOS information</param>
        /// <returns>The handler that will get the value out of the iOS</returns>
        public static GetHandler CreateGetHandler( FieldInfo fieldInfo ) {
            DynamicMethod dynamicGet = CreateGetDynamicMethod( fieldInfo.DeclaringType );
            ILGenerator getGenerator = dynamicGet.GetILGenerator( );

            getGenerator.Emit( OpCodes.Ldarg_0 );
            getGenerator.Emit( OpCodes.Ldfld, fieldInfo );
            BoxIfNeeded( fieldInfo.FieldType, getGenerator );
            getGenerator.Emit( OpCodes.Ret );

            return ( GetHandler )dynamicGet.CreateDelegate( typeof( GetHandler ) );
        }

        /// <summary>
        /// Creates a delegate that will set values in a property
        /// </summary>
        /// <param name="propertyInfo">Reflected property information</param>
        /// <returns>The delegate that will set the value into an object's property</returns>
        public static SetHandler CreateSetHandler( PropertyInfo propertyInfo ) {
            MethodInfo setMethodInfo = propertyInfo.GetSetMethod( true );
            DynamicMethod dynamicSet = CreateSetDynamicMethod( propertyInfo.DeclaringType );
            ILGenerator setGenerator = dynamicSet.GetILGenerator( );

            setGenerator.Emit( OpCodes.Ldarg_0 );
            setGenerator.Emit( OpCodes.Ldarg_1 );
            UnboxIfNeeded( setMethodInfo.GetParameters( )[ 0 ].ParameterType, setGenerator );
            setGenerator.Emit( OpCodes.Call, setMethodInfo );
            setGenerator.Emit( OpCodes.Ret );

            return ( SetHandler )dynamicSet.CreateDelegate( typeof( SetHandler ) );
        }

        /// <summary>
        /// Creates a handler that will set the value in a iOS of an object
        /// </summary>
        /// <param name="fieldInfo">The reflected iOS information</param>
        /// <returns>The delegate that will set the value in the iOS</returns>
        public static SetHandler CreateSetHandler( FieldInfo fieldInfo ) {
            DynamicMethod dynamicSet = CreateSetDynamicMethod( fieldInfo.DeclaringType );
            ILGenerator setGenerator = dynamicSet.GetILGenerator( );

            setGenerator.Emit( OpCodes.Ldarg_0 );
            setGenerator.Emit( OpCodes.Ldarg_1 );
            UnboxIfNeeded( fieldInfo.FieldType, setGenerator );
            setGenerator.Emit( OpCodes.Stfld, fieldInfo );
            setGenerator.Emit( OpCodes.Ret );

            return ( SetHandler )dynamicSet.CreateDelegate( typeof( SetHandler ) );
        }

        // CreateGetDynamicMethod
        private static DynamicMethod CreateGetDynamicMethod( Type type ) {
            return new DynamicMethod( "DynamicGet", typeof( object ), new Type[ ] { typeof( object ) }, type, true );
        }

        // CreateSetDynamicMethod
        private static DynamicMethod CreateSetDynamicMethod( Type type ) {
            return new DynamicMethod( "DynamicSet", typeof( void ), new Type[ ] { typeof( object ), typeof( object ) }, type, true );
        }

        // BoxIfNeeded
        private static void BoxIfNeeded( Type type, ILGenerator generator ) {
            if ( type.IsValueType ) {
                generator.Emit( OpCodes.Box, type );
            }
        }

        // UnboxIfNeeded
        private static void UnboxIfNeeded( Type type, ILGenerator generator ) {
            if ( type.IsValueType ) {
                generator.Emit( OpCodes.Unbox_Any, type );
            }
        }

    }
}
